﻿Imports System.IO
Imports Microsoft.VisualStudio.TeamFoundation.VersionControl.DiffMerge
Imports Microsoft.TeamFoundation.VersionControl.Common
Imports Microsoft.TeamFoundation.WorkItemTracking.Client
Imports Microsoft.TeamFoundation.VersionControl.Client
Imports Microsoft.TeamFoundation.Framework.Common
Imports Microsoft.TeamFoundation.Framework.Client
Imports Microsoft.TeamFoundation.Client
Imports Microsoft.TeamFoundation
Imports System.Collections.ObjectModel
Imports Microsoft.TeamFoundation.Server

Friend Enum TFSItemType
    Any
    File
    Folder
End Enum

Friend Enum TFSVersion
    Tfs2010
    Tfs2008OrOlder
End Enum

Friend Class TFSServer
    Private ReadOnly m_szName As String
    Private ReadOnly m_oServerUri As Uri
    Private ReadOnly m_eVersion As TFSVersion

    Public Sub New(ByVal name As String, ByVal serverUri As Uri, ByVal serverVersion As TFSVersion)
        m_szName = name.Trim()
        m_oServerUri = serverUri
        m_eVersion = serverVersion
    End Sub

    Public ReadOnly Property Name As String
        Get
            Return m_szName
        End Get
    End Property

    Public ReadOnly Property ServerUri As Uri
        Get
            Return m_oServerUri
        End Get
    End Property

    Public ReadOnly Property Version As TFSVersion
        Get
            Return m_eVersion
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return Name
    End Function
End Class

Friend Class TFSCollection
    Private ReadOnly m_szName As String
    Private ReadOnly m_szDescription As String
    Private ReadOnly m_oCollectionUri As Uri
    Private ReadOnly m_bPlaceHolder As Boolean

    Public Sub New(ByVal name As String, ByVal description As String, ByVal collectionUri As Uri, ByVal placeHolder As Boolean)
        m_szName = name
        m_szDescription = description
        m_oCollectionUri = collectionUri
        m_bPlaceHolder = placeHolder
    End Sub

    Public ReadOnly Property Name As String
        Get
            Return m_szName
        End Get
    End Property

    Public ReadOnly Property Description As String
        Get
            Return m_szDescription
        End Get
    End Property

    Public ReadOnly Property CollectionUri As Uri
        Get
            Return m_oCollectionUri
        End Get
    End Property

    Public ReadOnly Property PlaceHolder As Boolean
        Get
            Return m_bPlaceHolder
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return Name
    End Function
End Class

Friend Class TFSProject
    Private ReadOnly m_szName As String
    Private ReadOnly m_bSearchAll As Boolean

    Public Sub New(ByVal projectName As String, ByVal allIndicator As Boolean)
        m_szName = projectName
        m_bSearchAll = allIndicator
    End Sub

    Public ReadOnly Property Name As String
        Get
            Return m_szName
        End Get
    End Property

    Public ReadOnly Property SearchAll As Boolean
        Get
            Return m_bSearchAll
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return Name
    End Function
End Class

Friend Class TFSItem
    Private ReadOnly m_szName As String
    Private ReadOnly m_szServerPath As String
    Private ReadOnly m_eTfsItemType As TFSItemType
    Private ReadOnly m_szTeamProjectName As String

    Public Sub New(ByVal name As String, ByVal serverPath As String, ByVal itemType As TFSItemType, ByVal teamProjectName As String)
        m_szName = name
        m_szServerPath = serverPath
        m_eTfsItemType = itemType
        m_szTeamProjectName = teamProjectName
    End Sub

    Public ReadOnly Property Name As String
        Get
            Return m_szName
        End Get
    End Property

    Public ReadOnly Property ServerPath As String
        Get
            Return m_szServerPath
        End Get
    End Property

    Public ReadOnly Property ItemType As TFSItemType
        Get
            Return m_eTfsItemType
        End Get
    End Property

    Public ReadOnly Property TeamProjectName As String
        Get
            Return m_szTeamProjectName
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return Name
    End Function
End Class

Friend Class TFSArtifactInfo

    Private ReadOnly m_szPropertyName As String
    Private ReadOnly m_szPropertyValue As String

    Public Sub New(ByVal propertyName As String, ByVal propertyValue As String)
        m_szPropertyName = propertyName
        m_szPropertyValue = propertyValue
    End Sub

    Public ReadOnly Property PropertyName As String
        Get
            Return m_szPropertyName
        End Get
    End Property

    Public ReadOnly Property PropertyValue As String
        Get
            Return m_szPropertyValue
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return String.Format("Name: {0} Value: {1}", PropertyName, PropertyValue)
    End Function

End Class

Friend Class TFSArtifactInfoCollection
    Implements IEnumerable(Of List(Of TFSArtifactInfo))

    Private ReadOnly m_colProperties As List(Of List(Of TFSArtifactInfo)) = New List(Of List(Of TFSArtifactInfo))

    Public Sub Add(ByVal properties As List(Of TFSArtifactInfo))
        m_colProperties.Add(properties)
    End Sub

    Public Sub Merge(ByVal propertiesCollection As TFSArtifactInfoCollection)
        For Each oSet As List(Of TFSArtifactInfo) In propertiesCollection
            Add(oSet)
        Next
    End Sub

    Public ReadOnly Property Count
        Get
            Return m_colProperties.Count
        End Get
    End Property

    Public Function GetEnumerator() As IEnumerator(Of List(Of TFSArtifactInfo)) Implements IEnumerable(Of List(Of TFSArtifactInfo)).GetEnumerator
        Return GetEnumerator1()
    End Function

    Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
        Return m_colProperties.GetEnumerator()
    End Function

End Class

Friend Class TFSBranch
    Private ReadOnly m_szPath As String

    Public Sub New(ByVal branchPath As String)
        m_szPath = branchPath
    End Sub

    Public ReadOnly Property Name As String
        Get
            Return Path.Substring(Path.TrimEnd("/"c).LastIndexOf("/"c) + 1)
        End Get
    End Property

    Public ReadOnly Property Path As String
        Get
            Return m_szPath
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return Path
    End Function
End Class

Friend Class TFSMergeCandidate
    Private ReadOnly m_iChangesetId As Integer
    Private ReadOnly m_szComment As String
    Private ReadOnly m_szUser As String
    Private ReadOnly m_szLink As String
    Private ReadOnly m_dtDate As DateTime

    Public Sub New(ByVal changesetId As Integer, ByVal comment As String, ByVal user As String, ByVal changesetDate As DateTime, ByVal tfsServerUri As Uri)
        m_iChangesetId = changesetId
        m_szComment = comment.Trim()
        m_szUser = user.Trim()
        m_szLink = String.Format("{0}/web/cs.aspx?cs={1}", tfsServerUri.ToString(), m_iChangesetId)
        m_dtDate = changesetDate
    End Sub

    Public ReadOnly Property ChangesetDate As DateTime
        Get
            Return m_dtDate
        End Get
    End Property

    Public ReadOnly Property User() As String
        Get
            Return m_szUser
        End Get
    End Property

    Public ReadOnly Property Comment() As String
        Get
            Return m_szComment
        End Get
    End Property

    Public ReadOnly Property ChangesetId() As Integer
        Get
            Return m_iChangesetId
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return ChangesetId.ToString()
    End Function

    Public ReadOnly Property Link As String
        Get
            Return m_szLink
        End Get
    End Property
End Class

Friend Class TFSChangesetDetails
    Private ReadOnly m_clcChanges As ReadOnlyCollection(Of TFSChange)
    Private ReadOnly m_clcWorkItems As ReadOnlyCollection(Of TFSWorkItem)

    Public Sub New(ByVal changedItems As ReadOnlyCollection(Of TFSChange), ByVal workItems As ReadOnlyCollection(Of TFSWorkItem))
        m_clcChanges = changedItems
        m_clcWorkItems = workItems
    End Sub

    Public ReadOnly Property Changes() As ReadOnlyCollection(Of TFSChange)
        Get
            Return m_clcChanges
        End Get
    End Property

    Public ReadOnly Property WorkItems() As ReadOnlyCollection(Of TFSWorkItem)
        Get
            Return m_clcWorkItems
        End Get
    End Property
End Class

Friend Class TFSWorkItem
    Private ReadOnly m_iId As Integer
    Private ReadOnly m_szTitle As String
    Private ReadOnly m_szState As String
    Private ReadOnly m_szType As String
    Private ReadOnly m_szLink As String

    Public Sub New(ByVal id As Integer, ByVal title As String, ByVal state As String, ByVal type As String, ByVal tfsServerUri As Uri)
        m_iId = id
        m_szTitle = title
        m_szState = state
        m_szLink = String.Format("{0}/web/wi.aspx?id={1}", tfsServerUri.ToString(), id)
        m_szType = type
    End Sub

    Public ReadOnly Property Type() As String
        Get
            Return m_szType
        End Get
    End Property

    Public ReadOnly Property State() As String
        Get
            Return m_szState
        End Get
    End Property

    Public ReadOnly Property Title() As String
        Get
            Return m_szTitle
        End Get
    End Property

    Public ReadOnly Property Link As String
        Get
            Return m_szLink
        End Get
    End Property

    Public ReadOnly Property Id() As Integer
        Get
            Return m_iId
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return Title
    End Function
End Class

Friend Class TFSChange
    Private ReadOnly m_szName As String
    Private ReadOnly m_szServerPath As String
    Private ReadOnly m_szChangeTypes As String
    Private ReadOnly m_eTfsItemType As TFSItemType

    Public Sub New(ByVal name As String, ByVal serverPath As String, ByVal itemType As TFSItemType, ByVal changeTypes As String)
        m_szName = name
        m_szServerPath = serverPath
        m_eTfsItemType = itemType
        m_szChangeTypes = changeTypes.Trim()
    End Sub

    Public ReadOnly Property ChangeTypes() As String
        Get
            Return m_szChangeTypes
        End Get
    End Property

    Public ReadOnly Property Name As String
        Get
            Return m_szName
        End Get
    End Property

    Public ReadOnly Property ServerPath As String
        Get
            Return m_szServerPath
        End Get
    End Property

    Public ReadOnly Property ContainingFolder As String
        Get
            Return m_szServerPath.Substring(0, m_szServerPath.TrimEnd("/"c).LastIndexOf("/"c))
        End Get
    End Property

    Public ReadOnly Property ItemType As TFSItemType
        Get
            Return m_eTfsItemType
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return Name
    End Function
End Class

Friend Class TFSTeamQueryFolder

    Private ReadOnly m_clcQueries As ReadOnlyCollection(Of TFSTeamQueryDefinition)
    Private ReadOnly m_clcSubFolders As ReadOnlyCollection(Of TFSTeamQueryFolder)
    Private ReadOnly m_szName As String

    Public Sub New(ByVal folderName As String, ByVal teamQueries As List(Of TFSTeamQueryDefinition), ByVal childFolders As List(Of TFSTeamQueryFolder))
        m_clcQueries = teamQueries.AsReadOnly()
        m_szName = folderName.Trim()
        m_clcSubFolders = childFolders.AsReadOnly()
    End Sub

    Public ReadOnly Property Name As String
        Get
            Return m_szName
        End Get
    End Property

    Public ReadOnly Property Queries As ReadOnlyCollection(Of TFSTeamQueryDefinition)
        Get
            Return m_clcQueries
        End Get
    End Property

    Public ReadOnly Property SubFolders As ReadOnlyCollection(Of TFSTeamQueryFolder)
        Get
            Return m_clcSubFolders
        End Get
    End Property
End Class

Friend Class TFSTeamQueryDefinition

    Private ReadOnly m_szName As String
    Private ReadOnly m_szQueryText As String

    Public Sub New(ByVal queryName As String, ByVal queryText As String)
        m_szName = queryName.Trim()
        m_szQueryText = queryText.Trim()
    End Sub

    Public ReadOnly Property Name As String
        Get
            Return m_szName
        End Get
    End Property

    Public ReadOnly Property QueryText As String
        Get
            Return m_szQueryText
        End Get
    End Property
End Class

Friend Class TfsApi
    Private Sub New()
    End Sub

    Private Const ALL_PROJECTS As String = "<All>"
    Private Const DEFAULT_COLLECTION_NAME As String = "Default"

#Region "Private Methods"

    Private Shared Sub MergeExitHandler(ByVal sender As Object, ByVal e As EventArgs)
        Try
            'Dim szTmp As String
            'szTmp = String.Format("Sender: {0}{2}Args: {1}", sender.ToString(), e.GetType().ToString(), Environment.NewLine)
            'MessageBox.Show("MergeExitHandler called." & Environment.NewLine & szTmp, "MergeExitHandler")
        Catch
        End Try
    End Sub

    Private Shared Sub GetFileToSpecificLocation(ByVal versionControl As VersionControlServer, ByVal itemNumber As Integer, ByVal changesetNumber As Integer, ByVal localFilePath As String, ByVal makeWriteable As Boolean)
        Dim oItem As Item = versionControl.GetItem(itemNumber, changesetNumber, True)
        oItem.DownloadFile(localFilePath)
        If (makeWriteable) Then Helper.SetReadOnlyAttribute(localFilePath, False)
    End Sub

    Private Shared Function GetItemType(ByVal itemType As TFSItemType) As ItemType
        Select Case itemType
            Case TFSItemType.Any
                Return TFSItemType.Any
            Case TFSItemType.File
                Return TFSItemType.File
            Case TFSItemType.Folder
                Return TFSItemType.Folder
            Case Else
                Throw New InvalidOperationException("No mapping setup for TFS Item Type: " & itemType)
        End Select
    End Function

    Private Shared Function GetTFSItemType(ByVal itemTypeValue As ItemType) As TFSItemType
        Select Case itemTypeValue
            Case ItemType.Any
                Return TFSItemType.Any
            Case ItemType.File
                Return TFSItemType.File
            Case ItemType.Folder
                Return TFSItemType.Folder
            Case Else
                Throw New InvalidOperationException("No mapping setup for Item Type: " & itemTypeValue)
        End Select
    End Function

    Private Shared Function GetWorkItem(ByVal workItemId As Integer, ByVal teamProjectCollection As TfsTeamProjectCollection) As WorkItem
        Try
            Dim oWorkItemStore As WorkItemStore = teamProjectCollection.GetService(Of WorkItemStore)()
            Return oWorkItemStore.GetWorkItem(workItemId)
        Catch oExp As Exception 'DeniedOrNotExistsException
            Return Nothing
        End Try
    End Function

    Private Shared Function GetChangeTypes(ByVal changeType As VersionControl.Client.ChangeType) As String
        Return PendingChange.GetLocalizedStringForChangeType(changeType)
    End Function

    Private Shared Function GetFolder(ByVal oFolder As QueryFolder, ByVal teamOnly As Boolean) As TFSTeamQueryFolder
        If (oFolder.IsPersonal = teamOnly) Then Return Nothing

        Dim clcQueries As List(Of TFSTeamQueryDefinition) = New List(Of TFSTeamQueryDefinition)()
        Dim clcSubFolders As List(Of TFSTeamQueryFolder) = New List(Of TFSTeamQueryFolder)()

        For Each oChild As QueryItem In oFolder
            If (oChild.GetType() = GetType(QueryFolder)) Then
                Dim oChildFolder As TFSTeamQueryFolder = GetFolder(DirectCast(oChild, QueryFolder), teamOnly)
                If (oChildFolder IsNot Nothing) Then clcSubFolders.Add(oChildFolder)
            Else
                Dim oDefinition As QueryDefinition = DirectCast(oChild, QueryDefinition)
                clcQueries.Add(New TFSTeamQueryDefinition(oDefinition.Name, oDefinition.QueryText))
            End If
        Next

        Return New TFSTeamQueryFolder(oFolder.Name, clcQueries, clcSubFolders)
    End Function

#End Region

    Public Shared Function GetRegisteredServers() As List(Of TFSServer)
        Dim oServers As List(Of TFSServer) = New List(Of TFSServer)()

        For Each oServer As RegisteredConfigurationServer In RegisteredTfsConnections.GetConfigurationServers()
            oServers.Add(New TFSServer(oServer.Name, oServer.Uri, TFSVersion.TFS2010))
        Next

        ' Get TFS 2008 or older servers
        For Each oServer As RegisteredProjectCollection In RegisteredTfsConnections.GetLegacyProjectCollections()
            oServers.Add(New TFSServer(oServer.Name, oServer.Uri, TFSVersion.TFS2008OrOlder))
        Next

        Return oServers
    End Function

    Public Shared Function DisplayTeamProjectPicker() As String
        Using oPicker As TeamProjectPicker = New TeamProjectPicker(TeamProjectPickerMode.NoProject, False)
            Dim oResult As DialogResult = oPicker.ShowDialog()
            If (oResult = DialogResult.OK) Then
                Return oPicker.SelectedTeamProjectCollection.Name
            End If
        End Using

        Return String.Empty
    End Function

    Public Shared Function GetTeamProjectCollections(ByVal server As TFSServer) As List(Of TFSCollection)
        ' Do not change this value or else it will affect tab defaults

        Dim clcCollections As List(Of TFSCollection) = New List(Of TFSCollection)

        If (server.Version <> TFSVersion.TFS2010) Then
            clcCollections.Add(New TFSCollection(DEFAULT_COLLECTION_NAME, "TFS08 or older", server.ServerUri, True))
            Return clcCollections
        End If

        Using oConfigServer As TfsConfigurationServer = TfsConfigurationServerFactory.GetConfigurationServer(server.ServerUri)
            Dim oNodes As ReadOnlyCollection(Of CatalogNode) = oConfigServer.CatalogNode.QueryChildren(New Guid() {CatalogResourceTypes.ProjectCollection}, False, CatalogQueryOptions.None)

            Dim oLocationService As ILocationService = oConfigServer.GetService(GetType(ILocationService))

            For Each oNode As CatalogNode In oNodes
                Dim oResource As CatalogResource = oNode.Resource
                Dim oSD As ServiceDefinition = oResource.ServiceReferences("Location")

                Dim szUrl As String = oLocationService.LocationForCurrentConnection(oSD)

                Dim iIndex As Integer = szUrl.IndexOf("/Services/", StringComparison.Ordinal)

                If (iIndex > 0) Then
                    szUrl = szUrl.Substring(0, iIndex)
                End If

                Dim oURI As Uri = New Uri(szUrl)

                clcCollections.Add(New TFSCollection(oResource.DisplayName, oResource.Description, oURI, False))
            Next
        End Using

        Return clcCollections
    End Function

    Public Shared Function GetTeamProjects(ByVal collection As TFSCollection, ByVal includeAllOption As Boolean) As List(Of TFSProject)
        Dim clcProjects As List(Of TFSProject) = New List(Of TFSProject)()

        Using oTPC As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collection.CollectionUri)
            Dim oStructureService As ICommonStructureService = oTPC.GetService(GetType(ICommonStructureService))
            For Each oProject As ProjectInfo In oStructureService.ListAllProjects()
                clcProjects.Add(New TFSProject(oProject.Name, False))
            Next
        End Using

        If (includeAllOption AndAlso (clcProjects.Count > 0)) Then clcProjects.Insert(0, New TFSProject(ALL_PROJECTS, True))

        Return clcProjects
    End Function

    Public Shared Function SearchForItems(ByVal collection As TFSCollection, ByVal project As TFSProject, ByVal itemTypeToSearchFor As TFSItemType, ByVal searchText As String) As List(Of TFSItem)
        Dim clcItems As List(Of TFSItem) = New List(Of TFSItem)()

        Using oTPC As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collection.CollectionUri)
            Dim oVersionControlServer As VersionControlServer = oTPC.GetService(Of VersionControlServer)()

            Dim szProject As String = String.Empty
            If (Not project.SearchAll) Then szProject = project.Name & "/"

            Dim szSearchPath As String = String.Format("$/{0}{1}", szProject, searchText)
            Dim oItemSet As ItemSet = oVersionControlServer.GetItems(szSearchPath, VersionSpec.Latest, RecursionType.Full, DeletedState.NonDeleted, GetItemType(itemTypeToSearchFor), False)

            For Each oItem As Item In oItemSet.Items
                Dim szItemName As String = oItem.ServerItem.Substring(oItem.ServerItem.LastIndexOf("/"c) + 1)

                ' If we find an odd item type, just skip it for now
                If (oItem.ItemType = ItemType.Any) Then Continue For
                Dim szServerItem As String = oItem.ServerItem
                If (oItem.ItemType = ItemType.Folder) Then
                    szServerItem &= "/"
                End If

                Dim iFirstIndex As Integer = szServerItem.IndexOf("/"c) + 1
                Dim iSecondIndex As Integer = szServerItem.IndexOf("/"c, iFirstIndex)
                Dim szTeamProjectName As String = szServerItem.Substring(iFirstIndex, iSecondIndex - iFirstIndex)

                clcItems.Add(New TFSItem(szItemName, szServerItem, GetTFSItemType(oItem.ItemType), szTeamProjectName))
            Next
        End Using

        Return clcItems
    End Function

    Public Shared Function GetChildItems(ByVal collection As TFSCollection, ByVal project As TFSProject, ByVal itemTypeToSearchFor As TFSItemType, ByVal serverPath As String) As List(Of TFSItem)
        Dim clcItems As List(Of TFSItem) = New List(Of TFSItem)()

        Using oTPC As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collection.CollectionUri)
            Dim oVersionControlServer As VersionControlServer = oTPC.GetService(Of VersionControlServer)()

            If (serverPath(serverPath.Length - 1) = "/"c) Then serverPath = serverPath.TrimEnd("/"c)

            Dim szSearchPath As String = serverPath & "/*"
            Dim oItemSet As ItemSet = oVersionControlServer.GetItems(szSearchPath, VersionSpec.Latest, RecursionType.OneLevel, DeletedState.NonDeleted, GetItemType(itemTypeToSearchFor), False)

            For Each oItem As Item In oItemSet.Items
                Dim szItemName As String = oItem.ServerItem.Substring(oItem.ServerItem.LastIndexOf("/"c) + 1)

                ' If we find an odd item type, just skip it for now
                If (oItem.ItemType = ItemType.Any) Then Continue For
                Dim szServerItem As String = oItem.ServerItem
                If (oItem.ItemType = ItemType.Folder) Then
                    szServerItem &= "/"
                End If

                Dim iFirstIndex As Integer = szServerItem.IndexOf("/"c) + 1
                Dim iSecondIndex As Integer = szServerItem.IndexOf("/"c, iFirstIndex)
                Dim szTeamProjectName As String = szServerItem.Substring(iFirstIndex, iSecondIndex - iFirstIndex)

                clcItems.Add(New TFSItem(szItemName, szServerItem, GetTFSItemType(oItem.ItemType), szTeamProjectName))
            Next
        End Using

        Return clcItems
    End Function

    Public Shared Function GetLocalPath(ByVal collectionUri As Uri, ByVal serverPath As String) As String
        'Dim oWorkstation As Workstation = Workstation.Current
        Using oCollection As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collectionUri)

            Dim oVCS As VersionControlServer = oCollection.GetService(Of VersionControlServer)()

            Dim aoWorkspaces As Workspace()

            Try
                aoWorkspaces = oVCS.QueryWorkspaces(Nothing, Environment.UserDomainName & "\" & Environment.UserName, Environment.MachineName)
            Catch
                aoWorkspaces = oVCS.QueryWorkspaces(Nothing, Nothing, Environment.MachineName)
            End Try

            For Each oWorkspace As Workspace In aoWorkspaces
                Dim szLocalPath As String = oWorkspace.TryGetLocalItemForServerItem(serverPath)

                If (Not String.IsNullOrWhiteSpace(szLocalPath)) Then Return szLocalPath
                'Dim oWorkingFolder As WorkingFolder = oWorkspace.TryGetWorkingFolderForServerItem(serverPath)
                'Dim szTemp As String = oWorkingFolder.LocalItem
            Next

            Throw New InvalidOperationException("Unable to determine local path for server path: " & serverPath)
        End Using
    End Function

    Public Shared Function GetServerPath(ByVal collectionUri As String, ByVal localPath As String) As String
        'Dim oWorkstation As Workstation = Workstation.Current
        'Dim oWorkspaceInfo As WorkspaceInfo = oWorkstation.GetLocalWorkspaceInfo(serverPath)

        'Dim oCollection As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(New Uri(collectionUri))
        'Dim oWorkspace As Workspace = oWorkspaceInfo.GetWorkspace(oCollection)

        Throw New NotImplementedException("TODO")
    End Function

    Public Shared Function GetItemData(ByVal collectionUri As Uri, ByVal serverPath As String) As String
        Using oCollection As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collectionUri)
            Dim oVCS As VersionControlServer = oCollection.GetService(Of VersionControlServer)()
            Dim oItem As Item = oVCS.GetItem(serverPath, VersionSpec.Latest)

            If (oItem.ItemType <> ItemType.File) Then Return String.Empty

            Using oStreamReader As StreamReader = New StreamReader(oItem.DownloadFile())
                Return oStreamReader.ReadToEnd()
            End Using
        End Using
    End Function

    Public Shared Function GetVersionedItemProperties(ByVal collectionUri As Uri, ByVal serverPath As String) As List(Of TFSArtifactInfo)
        Dim clcProperties As List(Of TFSArtifactInfo) = New List(Of TFSArtifactInfo)()

        Using oCollection As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collectionUri)
            Dim oVCS As VersionControlServer = oCollection.GetService(Of VersionControlServer)()

            Dim oItemSpec As ItemSpec = New ItemSpec(serverPath, RecursionType.None)
            'Dim aoProperties As ArtifactPropertyValue() = oVCS.GetVersionedItemProperty(oItemSpec, VersionSpec.Latest, DeletedState.NonDeleted, ItemType.Any, "*")
            'For Each oProperty As ArtifactPropertyValue In aoProperties
            '    For Each oPropertyValue As PropertyValue In oProperty.PropertyValues
            '        clcProperties.Add(New TFSArtifactInfo(oPropertyValue.PropertyName, oPropertyValue.Value.ToString()))
            '    Next
            'Next

            Dim oItemSet As ItemSet = oVCS.GetItems(oItemSpec, VersionSpec.Latest, DeletedState.NonDeleted, ItemType.Any, False)
            For Each oItem As Item In oItemSet.Items
                For Each oPropertyValue As PropertyValue In oItem.Attributes
                    clcProperties.Add(New TFSArtifactInfo(oPropertyValue.PropertyName, oPropertyValue.Value.ToString()))
                Next
            Next
        End Using

        Return clcProperties
    End Function

    Public Shared Function GetBranches(ByVal collection As TFSCollection, ByVal project As TFSProject) As List(Of TFSBranch)
        Dim clcResults As List(Of TFSBranch) = New List(Of TFSBranch)()

        Using oTPC As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collection.CollectionUri)
            Dim oVcs As VersionControlServer = oTPC.GetService(Of VersionControlServer)()
            For Each oBranch As BranchObject In oVcs.QueryRootBranchObjects(RecursionType.Full)
                ' Verify this is for our project
                Dim szBranchPath As String = oBranch.Properties.RootItem.Item
                If (szBranchPath.IndexOf(project.Name, StringComparison.OrdinalIgnoreCase) < 0) Then Continue For

                ' Skip if it is a deleted branch
                If (oBranch.Properties.RootItem.IsDeleted) Then Continue For

                clcResults.Add(New TFSBranch(szBranchPath))
            Next
        End Using

        Return clcResults
    End Function

    Public Shared Function GetRelatedBranches(ByVal collection As TFSCollection, ByVal sourceBranch As TFSBranch) As List(Of TFSBranch)
        Dim clcResults As List(Of TFSBranch) = New List(Of TFSBranch)()

        Using oTPC As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collection.CollectionUri)
            Dim oVcs As VersionControlServer = oTPC.GetService(Of VersionControlServer)()

            Dim oItemIdentifier As ItemIdentifier = New ItemIdentifier(sourceBranch.Path)

            Dim aoBranches As BranchObject() = oVcs.QueryBranchObjects(oItemIdentifier, RecursionType.None)

            Dim oSourceBranch As BranchObject = aoBranches.FirstOrDefault(Function(oBranchToCheck)
                                                                              Return Not oBranchToCheck.Properties.RootItem.IsDeleted
                                                                          End Function)

            If (oSourceBranch Is Nothing) Then Throw New InvalidOperationException(String.Format("No branch was found at '{0}' in collection '{1}", sourceBranch.ToString(), collection.ToString()))

            For Each oBranch As ItemIdentifier In oSourceBranch.ChildBranches
                clcResults.Add(New TFSBranch(oBranch.Item))
            Next

            If (oSourceBranch.Properties.ParentBranch IsNot Nothing) Then clcResults.Add(New TFSBranch(oSourceBranch.Properties.ParentBranch.Item))
        End Using

        Return clcResults
    End Function

    Public Shared Function GetMergeCandidates(ByVal collection As TFSCollection, ByVal fromBranch As TFSBranch, ByVal toBranch As TFSBranch) As List(Of TFSMergeCandidate)
        Dim clcResults As List(Of TFSMergeCandidate) = New List(Of TFSMergeCandidate)()

        Using oTPC As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collection.CollectionUri)
            Dim oVcs As VersionControlServer = oTPC.GetService(Of VersionControlServer)()

            For Each oCandidate As MergeCandidate In oVcs.GetMergeCandidates(fromBranch.Path, toBranch.Path, RecursionType.Full)
                clcResults.Add(New TFSMergeCandidate(oCandidate.Changeset.ChangesetId, oCandidate.Changeset.Comment, oCandidate.Changeset.Owner, oCandidate.Changeset.CreationDate, oTPC.Uri))
            Next
        End Using

        Return clcResults
    End Function

    Public Shared Function GetChangesetDetails(ByVal collection As TFSCollection, ByVal changesetId As Integer) As TFSChangesetDetails
        Using oTPC As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collection.CollectionUri)
            Dim oVcs As VersionControlServer = oTPC.GetService(Of VersionControlServer)()

            Dim oChangeset As Changeset = oVcs.GetChangeset(changesetId, True, False)

            Dim clcChanges As List(Of TFSChange) = New List(Of TFSChange)()
            If (oChangeset.Changes.Length > 0) Then
                For Each oChange As Change In oChangeset.Changes
                    Dim szItemName As String = oChange.Item.ServerItem.Substring(oChange.Item.ServerItem.LastIndexOf("/"c) + 1)

                    ' If we find an odd item type, just skip it for now
                    If (oChange.Item.ItemType = ItemType.Any) Then Continue For
                    Dim szServerItem As String = oChange.Item.ServerItem
                    If (oChange.Item.ItemType = ItemType.Folder) Then
                        szServerItem &= "/"
                    End If

                    clcChanges.Add(New TFSChange(szItemName, szServerItem, GetTFSItemType(oChange.Item.ItemType), GetChangeTypes(oChange.ChangeType)))
                Next
            End If

            Dim clcWorkItems As List(Of TFSWorkItem) = New List(Of TFSWorkItem)()
            If (oChangeset.WorkItems.Length > 0) Then
                For Each oWorkItem As WorkItem In oChangeset.WorkItems
                    clcWorkItems.Add(New TFSWorkItem(oWorkItem.Id, oWorkItem.Title, oWorkItem.State, oWorkItem.Type.Name, oTPC.Uri))
                Next
            End If

            Return New TFSChangesetDetails(clcChanges.AsReadOnly(), clcWorkItems.AsReadOnly())
        End Using
    End Function

    Public Shared Sub MergeChangeset(ByVal parentForm As Form, ByVal tempDirectoryForApp As String, ByVal collection As TFSCollection, ByVal sourceBranch As TFSBranch, ByVal desinationBranch As TFSBranch, ByVal changesetId As Integer, ByVal overrideWorkItemId As Integer?)
        Const CONFLICT_FOLDER As String = "Conflicts"
        Const CONFLICT_LEFT_FOLDER As String = "Left"
        Const CONFLICT_RIGHT_FOLDER As String = "Right"
        Const CONFLICT_BASE_FOLDER As String = "Base"
        Const AUTO_MERGE As String = "AutoMerge"
        Const MERGE_TOOL As String = "Open With Merge Tool"
        Const ABORT_MERGE As String = "Abort"

        Dim szTempPath As String = Path.Combine(tempDirectoryForApp, "MruMerge")

        If (Directory.Exists(szTempPath)) Then
            Throw New InvalidOperationException("The temporary merge directory already exists.  Please remove it.  Path: " & szTempPath)
        End If

        Using oTPC As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collection.CollectionUri)
            Dim oVcs As VersionControlServer = oTPC.GetService(Of VersionControlServer)()

            oTPC.EnsureAuthenticated()

            Dim szSourceLocalPath As String = Path.Combine(szTempPath, sourceBranch.Name)
            Directory.CreateDirectory(szSourceLocalPath)
            Dim szDestinationLocalPath As String = Path.Combine(szTempPath, desinationBranch.Name)
            Directory.CreateDirectory(szDestinationLocalPath)
            Dim szConflictRootPath As String = Path.Combine(szTempPath, CONFLICT_FOLDER)
            Dim szConflictLeftPath As String = Path.Combine(szConflictRootPath, CONFLICT_LEFT_FOLDER)
            Dim szConflictRightPath As String = Path.Combine(szConflictRootPath, CONFLICT_RIGHT_FOLDER)
            Dim szConflictBasePath As String = Path.Combine(szConflictRootPath, CONFLICT_BASE_FOLDER)
            Directory.CreateDirectory(szConflictRootPath)
            Directory.CreateDirectory(szConflictLeftPath)
            Directory.CreateDirectory(szConflictRightPath)
            Directory.CreateDirectory(szConflictBasePath)

            Dim oWorkspace As Workspace = Nothing
            Try
                ' Create a workspace
                Dim aoWorkingFolders As WorkingFolder() = {New WorkingFolder(sourceBranch.Path, szSourceLocalPath, WorkingFolderType.Map, RecursionType.Full), New WorkingFolder(desinationBranch.Path, szDestinationLocalPath, WorkingFolderType.Map, RecursionType.Full)}
                Dim oCreateWorkspaceParameters As CreateWorkspaceParameters = New CreateWorkspaceParameters("TfsMerge")
                With oCreateWorkspaceParameters
                    .OwnerName = oVcs.AuthorizedUser
                    .Location = WorkspaceLocation.Server
                    .Comment = "Workspace for doing automatic merges."
                    .Folders = aoWorkingFolders
                End With
                oWorkspace = oVcs.CreateWorkspace(oCreateWorkspaceParameters)

                ' Merge in the changeset
                Dim oVersionSpec As VersionSpec = New ChangesetVersionSpec(changesetId)
                Dim oStatus As GetStatus = oWorkspace.Merge(sourceBranch.Path, desinationBranch.Path, oVersionSpec, oVersionSpec, LockLevel.None, RecursionType.Full, MergeOptions.None)

                ' See if there were any conflicts
                If (oStatus.NoActionNeeded) Then 'OrElse (oStatus.NumOperations = 0) Then
                    ' No changes were actually applied
                Else
                    If (oStatus.NumFailures > 0) Then
                        ' merge was performed and there are conflicts to resolve
                        Throw New InvalidOperationException(String.Format("Failures occurred merging changeset {0}.  You may try again, but you may need to merge this changeset manually.", changesetId))
                    ElseIf (oStatus.NumWarnings > 0) AndAlso (Not oStatus.HaveResolvableWarnings) Then
                        Throw New InvalidOperationException(String.Format("Unresolvable warnings occurred merging changeset {0}.  You may try again, but you may need to merge this changeset manually.", changesetId))
                    ElseIf (oStatus.NumConflicts > 0) Then
                        Dim bAbort As Boolean = False

                        ' merge was performed and there are conflicts to resolve
                        Dim aoConflicts As Conflict() = oWorkspace.QueryConflicts({desinationBranch.Path}, True)

                        If (aoConflicts.Length > 0) Then
                            Dim szResponse = Helper.DisplayMessageWithCustomButtons(parentForm, "Some conflicts were found.  How would you like to resolve them?", "Potentially Resolvable Conflicts Founds", New List(Of String) From {AUTO_MERGE, MERGE_TOOL, ABORT_MERGE})

                            If String.IsNullOrWhiteSpace(szResponse) OrElse szResponse.Equals(ABORT_MERGE, StringComparison.OrdinalIgnoreCase) Then
                                bAbort = True
                            Else
                                For Each oConflict As Conflict In aoConflicts
                                    Dim bUseMergeTool As Boolean = szResponse.Equals(MERGE_TOOL, StringComparison.OrdinalIgnoreCase)

                                    If (Not bUseMergeTool) AndAlso (Not oConflict.IsValidForAutoMerge) Then
                                        ' AutoMerge is not valid, so see if they want to change their choice
                                        Dim szChoice = Helper.DisplayMessageWithCustomButtons(parentForm, "Conflict does not appear to be valid for an automerge.  How do you want to proceed?", "Unable To AutoMerge Conflict", New List(Of String) From {MERGE_TOOL, ABORT_MERGE})
                                        If Not szChoice.Equals(MERGE_TOOL, StringComparison.OrdinalIgnoreCase) Then
                                            bAbort = True
                                            Exit For
                                        End If
                                        bUseMergeTool = True
                                    End If

                                    If bUseMergeTool Then
                                        ' Step one, get the three additional files we will need
                                        ' File Paths
                                        Dim szLeftFilePath As String = Path.Combine(szConflictLeftPath, oConflict.FileName)
                                        Dim szRightFilePath As String = Path.Combine(szConflictRightPath, oConflict.FileName)
                                        Dim szBaseFilePath As String = Path.Combine(szConflictBasePath, oConflict.FileName)
                                        Dim szResultsFilePath As String = Path.Combine(szConflictRootPath, oConflict.FileName)

                                        If (Not File.Exists(oConflict.TargetLocalItem)) Then
                                            ' The file has been automatically checked out, so we just need to explicitly get it
                                            GetFileToSpecificLocation(oVcs, oConflict.YourItemId, oConflict.YourVersion, oConflict.TargetLocalItem, True)
                                        End If

                                        ' Make sure the temp results file doesn't already exist
                                        If (File.Exists(szResultsFilePath)) Then File.Delete(szResultsFilePath)

                                        ' Get the files
                                        Try
                                            GetFileToSpecificLocation(oVcs, oConflict.TheirItemId, oConflict.TheirVersion, szLeftFilePath, True)
                                            GetFileToSpecificLocation(oVcs, oConflict.YourItemId, oConflict.YourVersion, szRightFilePath, True)
                                            GetFileToSpecificLocation(oVcs, oConflict.BaseItemId, oConflict.BaseVersion, szBaseFilePath, True)

                                            Const LEFT_FILE_TAG As String = "Source"
                                            Const RIGHT_FILE_TAG As String = "Target"
                                            Const BASE_FILE_TAG As String = "Base"
                                            Const RESULT_FILE_TAG As String = "Result"
                                            Dim szLeftFileLabel As String = String.Format("{0};C{1}", oConflict.TheirServerItem, oConflict.TheirVersion)
                                            Dim szRightFileLabel As String = String.Format("{0};C{1}", oConflict.YourServerItem, oConflict.YourVersion)
                                            Dim szBaseFileLabel As String = oConflict.BaseServerItem
                                            Dim szResultFileLabel As String = oConflict.TargetLocalItem

                                            'If (File.Exists(szResultFilePath)) Then File.Delete(szResultFilePath)

                                            ' ORDER VERY MUCH MATTERS
                                            Dim aszArgs As String() = {szLeftFilePath,
                                                                       szRightFilePath,
                                                                       szBaseFilePath,
                                                                       szResultsFilePath,
                                                                       String.Empty,
                                                                       String.Empty,
                                                                       String.Empty,
                                                                       String.Empty,
                                                                       LEFT_FILE_TAG,
                                                                       RIGHT_FILE_TAG,
                                                                       BASE_FILE_TAG,
                                                                       RESULT_FILE_TAG,
                                                                       szLeftFileLabel,
                                                                       szRightFileLabel,
                                                                       szBaseFileLabel,
                                                                       szResultFileLabel}
                                            Dim oAdvancedParameters As AdvancedToolParameters = New AdvancedToolParameters()
                                            oAdvancedParameters.CompareIsSourceReadOnly = True
                                            oAdvancedParameters.CompareIsTargetReadOnly = True
                                            oAdvancedParameters.IsModalDialog = True
                                            oAdvancedParameters.MergeServerGuid = oVcs.ServerGuid
                                            oAdvancedParameters.MergeSourceFileSpec = String.Format("{0};C{1}", oConflict.TheirServerItem, oConflict.TheirVersion)
                                            oAdvancedParameters.MergeTargetFileSpec = String.Format("{0};C{1}", oConflict.YourServerItem, oConflict.YourVersion)
                                            oAdvancedParameters.ShouldWaitForExit = True
                                            Dim oMerge As MergeProvider = New MergeProvider()
                                            Dim oResult As IToolExecutionResult = oMerge.Execute(AddressOf MergeExitHandler, oAdvancedParameters, aszArgs)
                                            If Not oResult.HasExited Then oResult.WaitForOperationEnd()

                                            If oResult.ExitCode = 0 Then
                                                File.Copy(szResultsFilePath, oConflict.TargetLocalItem, True)
                                                oConflict.Resolution = Resolution.AcceptMerge
                                                oConflict.MergedFileName = oConflict.TargetLocalItem
                                                oWorkspace.ResolveConflict(oConflict)
                                            Else
                                                If (Not String.IsNullOrWhiteSpace(oResult.ExitMessage)) Then Throw New InvalidOperationException(String.Format("Unexpected exit code returned from merge: {0}{2}{1}", oResult.ExitCode, oResult.ExitMessage, Environment.NewLine))

                                                ' otherwise let if fall through to the abort
                                            End If
                                        Finally
                                            If (File.Exists(szLeftFilePath)) Then File.Delete(szLeftFilePath)
                                            If (File.Exists(szRightFilePath)) Then File.Delete(szRightFilePath)
                                            If (File.Exists(szBaseFilePath)) Then File.Delete(szBaseFilePath)
                                        End Try
                                    Else
                                        ' Try an automerge
                                        If (oWorkspace.MergeContent(oConflict, False)) Then
                                            If (oConflict.ContentMergeSummary IsNot Nothing) AndAlso (oConflict.ContentMergeSummary.TotalConflicting = 0) Then
                                                oConflict.Resolution = Resolution.AcceptMerge
                                                oWorkspace.ResolveConflict(oConflict)
                                            End If
                                        End If
                                    End If

                                    If (Not oConflict.IsResolved) Then
                                        bAbort = True
                                        Exit For
                                    End If
                                Next
                            End If
                        End If

                        If (bAbort) Then Throw New InvalidOperationException(String.Format("Conflicts occurred merging changeset {0}.  You will need to merge this changeset manually.", changesetId))
                    ElseIf (oStatus.NumOperations = oStatus.NumUpdated) Then
                        ' Means that merges were performed, just need to check in pended merge changes
                    ElseIf (oStatus.NumOperations > 0) AndAlso (oStatus.NumWarnings > 0) AndAlso (oStatus.HaveResolvableWarnings) Then
                        ' Means that merges were performed, but all conflicts were resolved automatically. Need to check in pended merge changes
                    ElseIf (oStatus.NumOperations > 0) AndAlso (oStatus.NumUpdated > 0) AndAlso (oStatus.NumWarnings = 0) Then
                        ' Means that our merge is probably just deletes only.  Since items were updated and there were no warning or conflicts, try to commit
                    Else
                        Throw New ApplicationException(String.Format("Unrecognized status returned for changeset {7}.  NoActionNeeded: {1} HaveResolvableWarnings: {0} NumOperations: {2} NumConflicts: {3} NumFailures: {4} NumUpdated: {5} NumWarnings: {6}", oStatus.HaveResolvableWarnings, oStatus.NoActionNeeded, oStatus.NumOperations, oStatus.NumConflicts, oStatus.NumFailures, oStatus.NumUpdated, oStatus.NumWarnings, changesetId))
                    End If
                End If

                ' Commit the changes
                Dim aoPendingChanges As PendingChange() = oWorkspace.GetPendingChanges()
                If (aoPendingChanges.Length > 0) Then

                    Dim oChangeset As Changeset = oVcs.GetChangeset(changesetId)

                    Dim szNewComment As String = String.Format("[Merged by {0}\{1}] Original Changeset: {2} Owner: {3} Comment: {4}", Environment.UserDomainName, Environment.UserName, oChangeset.ChangesetId, oChangeset.Owner, oChangeset.Comment)

                    ' Get the override work item, if specified
                    Dim oOverrideWorkItem As WorkItem = Nothing
                    Try
                        If (overrideWorkItemId.HasValue) Then oOverrideWorkItem = GetWorkItem(overrideWorkItemId.Value, oTPC)

                        Dim colWorkItemCheckinInfo As List(Of WorkItemCheckinInfo) = New List(Of WorkItemCheckinInfo)()

                        If (oOverrideWorkItem Is Nothing) Then
                            For Each oItem As WorkItem In oChangeset.WorkItems
                                colWorkItemCheckinInfo.Add(New WorkItemCheckinInfo(oItem, WorkItemCheckinAction.Associate))
                                oItem.Close()
                            Next
                        Else
                            colWorkItemCheckinInfo.Add(New WorkItemCheckinInfo(oOverrideWorkItem, WorkItemCheckinAction.Associate))
                        End If

                        If (colWorkItemCheckinInfo.Count = 0) Then
                            ' We have no work item, so need to override any check-in policy
                            oWorkspace.CheckIn(oWorkspace.GetPendingChanges(), Environment.UserName, szNewComment, Nothing, Nothing, New PolicyOverrideInfo("Auto checkin", Nothing), CheckinOptions.ValidateCheckinOwner And CheckinOptions.SuppressEvent)
                        Else
                            ' We have a work item, so associate to it
                            oWorkspace.CheckIn(aoPendingChanges, Environment.UserName, szNewComment, Nothing, colWorkItemCheckinInfo.ToArray(), Nothing, CheckinOptions.ValidateCheckinOwner And CheckinOptions.SuppressEvent)
                        End If
                    Finally
                        If (oOverrideWorkItem IsNot Nothing) Then oOverrideWorkItem.Close()
                    End Try
                End If
            Catch oExp As Exception
                If (oWorkspace IsNot Nothing) Then
                    ' Undo any pending changes
                    Dim aoPendingChanges As PendingChange() = oWorkspace.GetPendingChanges()
                    If (aoPendingChanges.Length > 0) Then oWorkspace.Undo(aoPendingChanges)
                End If

                Throw
            Finally
                If (oWorkspace IsNot Nothing) Then
                    oWorkspace.Delete()
                End If
                ' Clear the read-only attributes
                Dim aszFiles As String() = Directory.GetFiles(szTempPath, "*", SearchOption.AllDirectories)
                Helper.SetReadOnlyAttribute(aszFiles, False)
                Directory.Delete(szTempPath, True)
            End Try
        End Using
    End Sub

    Public Shared Function GetTeamQueries(ByVal collection As TFSCollection, ByVal teamProject As TFSProject) As List(Of TFSTeamQueryFolder)
        Dim clcResults As List(Of TFSTeamQueryFolder) = New List(Of TFSTeamQueryFolder)()

        Using oTPC As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collection.CollectionUri)
            Dim oWorkItemStore As WorkItemStore = oTPC.GetService(Of WorkItemStore)()
            Dim oTeamProject As Project = Nothing
            For Each oProject As Project In oWorkItemStore.Projects
                If (oProject.Name.Equals(teamProject.Name, StringComparison.OrdinalIgnoreCase)) Then
                    oTeamProject = oProject
                    Exit For
                End If
            Next

            If (oTeamProject IsNot Nothing) Then
                For Each oFolder As QueryFolder In oTeamProject.QueryHierarchy
                    Dim oTeamQueryFolder As TFSTeamQueryFolder = GetFolder(oFolder, True)
                    If (oTeamQueryFolder IsNot Nothing) Then clcResults.Add(oTeamQueryFolder)
                Next
            End If
        End Using

        Return clcResults
    End Function

    Public Shared Function RunWorkItemQuery(ByVal collection As TFSCollection, ByVal project As TFSProject, ByVal queryText As String) As DataTable
        If (String.IsNullOrWhiteSpace(queryText)) Then Throw New ArgumentNullException("queryText", "No work item query text was specified.")

        Dim oDtResults As DataTable = Nothing
        Try
            Using oTPC As TfsTeamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(collection.CollectionUri)
                Dim oWorkItemStore As WorkItemStore = oTPC.GetService(Of WorkItemStore)()

                ' Repleace all @ because this code runs outside of Visual Studio environment.
                Dim oResults As WorkItemCollection = oWorkItemStore.Query(queryText.Replace("@project", "'" & project.Name & "'").Replace("@Me", String.Format("'{0}'", Environment.UserName)))

                oDtResults = New DataTable("Results")

                For Each oField As FieldDefinition In oResults.DisplayFields
                    Select Case oField.SystemType
                        Case GetType(Integer)
                            oDtResults.Columns.Add(oField.Name, GetType(Integer))
                        Case GetType(Decimal)
                            oDtResults.Columns.Add(oField.Name, GetType(Decimal))
                        Case GetType(Double)
                            oDtResults.Columns.Add(oField.Name, GetType(Double))
                        Case GetType(DateTime)
                            oDtResults.Columns.Add(oField.Name, GetType(DateTime))
                        Case GetType(Boolean)
                            oDtResults.Columns.Add(oField.Name, GetType(Boolean))
                        Case Else
                            oDtResults.Columns.Add(oField.Name, GetType(String))
                    End Select
                Next

                For Each oResultWI As WorkItem In oResults
                    Dim oDrNew As DataRow = oDtResults.NewRow()

                    For Each oDcColumn As DataColumn In oDtResults.Columns
                        Dim szName As String = oDcColumn.ColumnName
                        oDrNew.Item(szName) = oResultWI.Fields(szName).Value
                    Next

                    oDtResults.Rows.Add(oDrNew)
                Next

                oDtResults.AcceptChanges()
                Return oDtResults
            End Using
        Catch
            If (oDtResults IsNot Nothing) Then oDtResults.Dispose()
            Throw
        End Try
    End Function

End Class
